
/*
 // jQuery multiSelect
 //
 // A Beautiful Site (http://abeautifulsite.net/)
 // Usage: $('#control_id').multiSelect( options, callback)
 // Options:  selectAll          - whether or not to display the Select All option; true/false, default = true
 //           selectAllText      - text to display for selecting/unselecting all options simultaneously
 //           noneSelected       - text to display when there are no selected items in the list
 //           oneOrMoreSelected  - text to display when there are one or more selected items in the list
 //                                (note: you can use % as a placeholder for the number of items selected).
 //                                Use * to show a comma separated list of all selected; default = '% selected'
 //           optGroupSelectable - whether or not optgroups are selectable if you use them; true/false, default = false
 //           listHeight         - the max height of the droptdown options
 //
 //
 */
if (jQuery) 
	(function($){
		// render the html for a single option
		function renderOption(id, option){
			var html = '<label><input type="checkbox" class="' + id + '" value="' + option.value + '"';
			if (option.selected) {
				html += ' checked="checked"';
			}
			html += ' />' + option.text + '</label>';
			return html;
		}
		
		// render the html for the options/optgroups
		function renderOptions(id, options, o){
			var html = "";
			for (var i = 0; i < options.length; i++) {
				if (options[i].optgroup) {
					html += '<label class="optGroup">';
					if (o.optGroupSelectable) {
						html += '<input type="checkbox" class="optGroup" />' + options[i].optgroup;
					} else {
						html += options[i].optgroup;
					}
					html += '</label><div class="optGroupContainer">';
					html += renderOptions(id, options[i].options, o);
					html += '</div>';
				} else {
					html += renderOption(id, options[i]);
				}
			}
			return html;
		}
		
		// Building the actual options
		function buildOptions(options){
			var multiSelect = $(this);
			var multiSelectOptions = multiSelect.next('.multiSelectOptions');
			var o = multiSelect.data("config");
			var callback = multiSelect.data("callback");
			
			// clear the existing options
			multiSelectOptions.html("");
			var html = "";
			
			// if we should have a select all option then add it
			if (o.selectAll) {
				html += '<label class="selectAll"><input type="checkbox" class="selectAll" />' + o.selectAllText + '</label>';
			}
			
			// generate the html for the new options
			html += renderOptions(multiSelect.attr('id'), options, o);
			multiSelectOptions.html(html);
			// variables needed to account for width changes due to a scrollbar
			var initialWidth = multiSelectOptions.width();
			var hasScrollbar = false;
			
			// set the height of the dropdown options
			if (multiSelectOptions.height() > o.listHeight) {
				multiSelectOptions.css("height", o.listHeight + 'px');
				hasScrollbar = true;
			} else {
				multiSelectOptions.css("height", '');
			}
			
			// if the there is a scrollbar and the browser did not already handle adjusting the width (i.e. Firefox) then we will need to manaually add the scrollbar width
			var scrollbarWidth = hasScrollbar && (initialWidth == multiSelectOptions.width()) ? 17 : 0;
			
			// set the width of the dropdown options
			if ((multiSelectOptions.width() + scrollbarWidth) < multiSelect.outerWidth()) {
				multiSelectOptions.css("width", multiSelect.outerWidth() - 2/*border*/ + 'px');
			} else {
				multiSelectOptions.css("width", (multiSelectOptions.width() + scrollbarWidth) + 'px');
			}
			
			// Apply bgiframe if available on IE6
			if ($.fn.bgiframe) 
				multiSelect.next('.multiSelectOptions').bgiframe({
					width: multiSelectOptions.width(),
					height: multiSelectOptions.height()
				});
			
			// Handle selectAll oncheck
			if (o.selectAll) {
				multiSelectOptions.find('INPUT.selectAll').click(function(){
					// update all the child checkboxes
					multiSelectOptions.find('INPUT:checkbox').attr('checked', $(this).attr('checked')).parent("LABEL").toggleClass('checked', $(this).attr('checked'));
				});
			}
			
			// Handle OptGroup oncheck
			if (o.optGroupSelectable) {
				multiSelectOptions.addClass('optGroupHasCheckboxes');
				
				multiSelectOptions.find('INPUT.optGroup').click(function(){
					// update all the child checkboxes
					$(this).parent().next().find('INPUT:checkbox').attr('checked', $(this).attr('checked')).parent("LABEL").toggleClass('checked', $(this).attr('checked'));
				});
			}
			
			// Handle all checkboxes
			multiSelectOptions.find('INPUT:checkbox').click(function(){
				// set the label checked class
				$(this).parent("LABEL").toggleClass('checked', $(this).attr('checked'));
				
				updateSelected.call(multiSelect);
				multiSelect.focus();
				if ($(this).parent().parent().hasClass('optGroupContainer')) {
					updateOptGroup.call(multiSelect, $(this).parent().parent().prev());
				}
				if (callback) {
					callback($(this));
				}
			});
			
			// Initial display
			multiSelectOptions.each(function(){
				$(this).find('INPUT:checked').parent().addClass('checked');
			});
			
			// Initialize selected and select all 
			updateSelected.call(multiSelect);
			
			// Initialize optgroups
			if (o.optGroupSelectable) {
				multiSelectOptions.find('LABEL.optGroup').each(function(){
					updateOptGroup.call(multiSelect, $(this));
				});
			}
			
			// Handle hovers
			multiSelectOptions.find('LABEL:has(INPUT)').hover(function(){
				$(this).parent().find('LABEL').removeClass('hover');
				$(this).addClass('hover');
			}, function(){
				$(this).parent().find('LABEL').removeClass('hover');
			});
			
			// Keyboard
			multiSelect.keydown(function(e){
			
				var multiSelectOptions = $(this).next('.multiSelectOptions');
				
				// Is dropdown visible?
				if (multiSelectOptions.css('visibility') != 'hidden') {
					// Dropdown is visible
					// Tab
					if (e.keyCode == 9) {
						$(this).addClass('focus').trigger('click'); // esc, left, right - hide
						$(this).focus().next(':input').focus();
						return true;
					}
					
					// ESC, Left, Right
					if (e.keyCode == 27 || e.keyCode == 37 || e.keyCode == 39) {
						// Hide dropdown
						$(this).addClass('focus').trigger('click');
					}
					// Down || Up
					if (e.keyCode == 40 || e.keyCode == 38) {
						var allOptions = multiSelectOptions.find('LABEL');
						var oldHoverIndex = allOptions.index(allOptions.filter('.hover'));
						var newHoverIndex = -1;
						
						// if there is no current highlighted item then highlight the first item
						if (oldHoverIndex < 0) {
							// Default to first item
							multiSelectOptions.find('LABEL:first').addClass('hover');
						}     // else if we are moving down and there is a next item then move
						else 
							if (e.keyCode == 40 && oldHoverIndex < allOptions.length - 1) {
								newHoverIndex = oldHoverIndex + 1;
							}     // else if we are moving up and there is a prev item then move
							else 
								if (e.keyCode == 38 && oldHoverIndex > 0) {
									newHoverIndex = oldHoverIndex - 1;
								}
						
						if (newHoverIndex >= 0) {
							$(allOptions.get(oldHoverIndex)).removeClass('hover'); // remove the current highlight
							$(allOptions.get(newHoverIndex)).addClass('hover'); // add the new highlight
							// Adjust the viewport if necessary
							adjustViewPort(multiSelectOptions);
						}
						
						return false;
					}
					
					// Enter, Space
					if (e.keyCode == 13 || e.keyCode == 32) {
						var selectedCheckbox = multiSelectOptions.find('LABEL.hover INPUT:checkbox');
						
						// Set the checkbox (and label class)
						selectedCheckbox.attr('checked', !selectedCheckbox.attr('checked')).parent("LABEL").toggleClass('checked', selectedCheckbox.attr('checked'));
						
						// if the checkbox was the select all then set all the checkboxes
						if (selectedCheckbox.hasClass("selectAll")) {
							multiSelectOptions.find('INPUT:checkbox').attr('checked', selectedCheckbox.attr('checked')).parent("LABEL").addClass('checked').toggleClass('checked', selectedCheckbox.attr('checked'));
						}
						
						updateSelected.call(multiSelect);
						
						if (callback) 
							callback($(this));
						return false;
					}
					
					// Any other standard keyboard character (try and match the first character of an option)
					if (e.keyCode >= 33 && e.keyCode <= 126) {
						// find the next matching item after the current hovered item
						var match = multiSelectOptions.find('LABEL:startsWith(' + String.fromCharCode(e.keyCode) + ')');
						
						var currentHoverIndex = match.index(match.filter('LABEL.hover'));
						
						// filter the set to any items after the current hovered item
						var afterHoverMatch = match.filter(function(index){
							return index > currentHoverIndex;
						});
						
						// if there were no item after the current hovered item then try using the full search results (filtered to the first one)
						match = (afterHoverMatch.length >= 1 ? afterHoverMatch : match).filter("LABEL:first");
						
						if (match.length == 1) {
							// if we found a match then move the hover
							multiSelectOptions.find('LABEL.hover').removeClass('hover');
							match.addClass('hover');
							
							adjustViewPort(multiSelectOptions);
						}
					}
				} else {
					// Dropdown is not visible
					if (e.keyCode == 38 || e.keyCode == 40 || e.keyCode == 13 || e.keyCode == 32) { //up, down, enter, space - show
						// Show dropdown
						$(this).removeClass('focus').trigger('click');
						multiSelectOptions.find('LABEL:first').addClass('hover');
						return false;
					}
					//  Tab key
					if (e.keyCode == 9) {
						// Shift focus to next INPUT element on page
						multiSelectOptions.next(':input').focus();
						return true;
					}
				}
				// Prevent enter key from submitting form
				if (e.keyCode == 13) 
					return false;
			});
		}
		
		// Adjust the viewport if necessary
		function adjustViewPort(multiSelectOptions){
			// check for and move down
			var selectionBottom = multiSelectOptions.find('LABEL.hover').position().top + multiSelectOptions.find('LABEL.hover').outerHeight();
			
			if (selectionBottom > multiSelectOptions.innerHeight()) {
				multiSelectOptions.scrollTop(multiSelectOptions.scrollTop() + selectionBottom - multiSelectOptions.innerHeight());
			}
			
			// check for and move up						
			if (multiSelectOptions.find('LABEL.hover').position().top < 0) {
				multiSelectOptions.scrollTop(multiSelectOptions.scrollTop() + multiSelectOptions.find('LABEL.hover').position().top);
			}
		}
		
		// Update the optgroup checked status
		function updateOptGroup(optGroup){
			var multiSelect = $(this);
			var o = multiSelect.data("config");
			
			// Determine if the optgroup should be checked
			if (o.optGroupSelectable) {
				var optGroupSelected = true;
				$(optGroup).next().find('INPUT:checkbox').each(function(){
					if (!$(this).attr('checked')) {
						optGroupSelected = false;
						return false;
					}
				});
				
				$(optGroup).find('INPUT.optGroup').attr('checked', optGroupSelected).parent("LABEL").toggleClass('checked', optGroupSelected);
			}
		}
		
		// Update the textbox with the total number of selected items, and determine select all
		function updateSelected(){
			var multiSelect = $(this);
			var multiSelectOptions = multiSelect.next('.multiSelectOptions');
			var o = multiSelect.data("config");
			var i = 0;
			var selectAll = true;
			var display = '';
			var vlist = '';
			multiSelectOptions.find('INPUT:checkbox').not('.selectAll, .optGroup').each(function(){
				if ($(this).attr('checked')) {
					i++;
					display = display + $(this).parent().text() + ', ';
					vlist = vlist + $(this).val()+',';
				} else 
					selectAll = false;
			});
			// trim any end comma and surounding whitespace
			display = display.replace(/\s*\,\s*$/, '');
			vlist =  vlist.replace(/\s*\,\s*$/, '');
			if (i == 0) {
				multiSelect.find("span").html(o.noneSelected);
				multiSelect.attr('list','');
			} else {
				if (o.oneOrMoreSelected == '*') {
					multiSelect.find("span").html(display);
					multiSelect.attr("title", display);
					multiSelect.attr('list',vlist);
				} else {
					multiSelect.find("span").html(o.oneOrMoreSelected.replace('%', i));
				}
			}
			// Determine if Select All should be checked
			if (o.selectAll) {
				multiSelectOptions.find('INPUT.selectAll').attr('checked', selectAll).parent("LABEL").toggleClass('checked', selectAll);
			}
		}
		
		$.extend($.fn, {
			multiSelect: function(o, callback){
				// Default options
				if (!o) 
					o = {};
				if (o.selectAll == undefined) 
					o.selectAll = true;
				if (o.selectAllText == undefined) 
					o.selectAllText = "Select All";
				if (o.noneSelected == undefined) 
					o.noneSelected = 'Select options';
				if (o.oneOrMoreSelected == undefined) 
					o.oneOrMoreSelected = '% selected';
				if (o.optGroupSelectable == undefined) 
					o.optGroupSelectable = false;
				if (o.listHeight == undefined) 
					o.listHeight = 150;
				
				// Initialize each multiSelect
				$(this).each(function(){
					var select = $(this);
					var html = '<a list="" href="javascript:;" class="multiSelect"><span></span></a>';
					html += '<div class="multiSelectOptions" style="position: absolute; z-index:1000; visibility: hidden;"></div>';
					$(select).after(html);
					var multiSelect = $(select).next('.multiSelect');
					var multiSelectOptions = multiSelect.next('.multiSelectOptions');
					// if the select object had a width defined then match the new multilsect to it
					multiSelect.find("span").css("width", $(select).width() + 'px');
					// Attach the config options to the multiselect
					multiSelect.data("config", o);
					
					// Attach the callback to the multiselect
					multiSelect.data("callback", callback);
					
					// Serialize the select options into json options
					var options = [];
					$(select).children().each(function(){
						if (this.tagName.toUpperCase() == 'OPTGROUP') {
							var suboptions = [];
							options.push({
								optgroup: $(this).attr('label'),
								options: suboptions
							});
							
							$(this).children('OPTION').each(function(){
								if ($(this).val() != '') {
									suboptions.push({
										text: $(this).html(),
										value: $(this).val(),
										selected: $(this).attr('selected')
									});
								}
							});
						} else 
							if (this.tagName.toUpperCase() == 'OPTION') {
								if ($(this).val() != '') {
									options.push({
										text: $(this).html(),
										value: $(this).val(),
										selected: $(this).attr('selected')
									});
								}
							}
					});
					// Eliminate the original form element
					$(select).remove();
					// Add the id that was on the original select element to the new input
					multiSelect.attr("id", $(select).attr("id"));
					// Build the dropdown options
					buildOptions.call(multiSelect, options);
					// Events
					multiSelect.hover(function(){
						$(this).addClass('hover');
					}, function(){
						$(this).removeClass('hover');
					}).click(function(){
						// Show/hide on click
						if ($(this).hasClass('active')) {
							$(this).multiSelectOptionsHide();
						} else {
							$(this).multiSelectOptionsShow();
						}
						return false;
					}).focus(function(){
						// So it can be styled with CSS
						$(this).addClass('focus');
					}).blur(function(){
						// So it can be styled with CSS
						$(this).removeClass('focus');
					});
					
					// Add an event listener to the window to close the multiselect if the user clicks off
					$(document).click(function(event){
						// If somewhere outside of the multiselect was clicked then hide the multiselect
						if (!($(event.target).parents().andSelf().is('.multiSelectOptions'))) {
							multiSelect.multiSelectOptionsHide();
						}
					});
					try{//IE hack
						$(document).ready(function(){
							$("#contentView").parent().scroll(function(){
								$(document).click();
							});
						})
					}catch(e){}
				});
			},
			
			// Update the dropdown options
			multiSelectOptionsUpdate: function(options){
				buildOptions.call($(this), options);
			},
			
			// Hide the dropdown
			multiSelectOptionsHide: function(){
				$(this).removeClass('active').removeClass('hover').next('.multiSelectOptions').css('visibility', 'hidden');
			},
			
			// Show the dropdown
			multiSelectOptionsShow: function(){
				var multiSelect = $(this);
				var multiSelectOptions = multiSelect.next('.multiSelectOptions');
				var o = multiSelect.data("config");
				
				// Hide any open option boxes
				$('.multiSelect').multiSelectOptionsHide();
				multiSelectOptions.find('LABEL').removeClass('hover');
				multiSelect.addClass('active').next('.multiSelectOptions').css('visibility', 'visible');
				multiSelect.focus();
				
				// reset the scroll to the top
				multiSelect.next('.multiSelectOptions').scrollTop(0);
				
				// Position it
				var offset = multiSelect.position();
				multiSelect.next('.multiSelectOptions').css({
					top: offset.top + $(this).outerHeight() + 'px'
				});
				multiSelect.next('.multiSelectOptions').css({
					left: offset.left + 'px'
				});
			},
			
			// get a coma-delimited list of selected values
			selectedValuesString: function(){
				var selectedValues = "";
				$(this).next('.multiSelectOptions').find('INPUT:checkbox:checked').not('.optGroup, .selectAll').each(function(){
					selectedValues += $(this).attr('value') + ",";
				});
				// trim any end comma and surounding whitespace
				return selectedValues.replace(/\s*\,\s*$/, '');
			}
		});
		
		// add a new ":startsWith" search filter
		$.expr[":"].startsWith = function(el, i, m){
			var search = m[3];
			if (!search) 
				return false;
			return eval("/^[/s]*" + search + "/i").test($(el).text());
		};
		
	})(jQuery);
